﻿using CsvHelper;
using MongoDB.Bson.Serialization.Attributes;
using PerfTestLib.Utility;
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;

namespace PerfTestLib.Result
{
    public class TestSpecResult
    {
        public Guid Id { get; set; }

        public string SpecId { get; set; }

        public List<TestSpecIterationResult> IterationResults { get; set; }

        public long Ms { get; set; }

        public TestSpecResultStatus Status { get; set; }

        private Exception _exception;

        [BsonIgnore]
        public Exception Exception
        {
            get
            {
                return _exception;
            }
            set
            {
                if (_exception != null)
                {
                    this.ExceptionString = _exception.ToString();
                }
                else
                {
                    this.ExceptionString = null;
                }

                _exception = value;
            }
        }

        public string ExceptionString { get; set; }

        public DateTime StartDate { get; set; }


        public TestSpecResult()
        {
            this.Id = Guid.NewGuid();
            this.IterationResults = new List<TestSpecIterationResult>();
            this.StartDate = DateTime.Now;
        }

        public void SaveToCsv()
        {
            string path = TestFileUtility.GetTestSpecOutputFileLocation(this.SpecId, DateTime.Now, true);
            this.SaveToCsv(path);
        }

        public void SaveToCsv(string path)
        {
            List<List<FlattenObjectField>> table = FlattenObject.FlattenTable(this);

            if (table != null && table.Count > 0)
            {

                // get column names
                List<string> columnNames = table.First().Select(el => el.Key).ToList();

                // create CSV file
                using (FileStream fs = new FileStream(path, FileMode.Create, FileAccess.Write))
                {
                    // text writer
                    using (StreamWriter sw = new StreamWriter(fs))
                    {
                        // CSV writer
                        using (CsvWriter writer = new CsvWriter(sw))
                        {
                            // write columns
                            columnNames.ForEach(col =>
                            {
                                writer.WriteField(col);
                            });

                            // go to next record
                            writer.NextRecord();                            

                            // aggregate table
                            var aggTable = AggregateTable(table);

                            // write rows
                            aggTable.ForEach(row =>
                                {
                                    row.ForEach(col =>
                                        {
                                            writer.WriteField(col.Value);
                                        });

                                    writer.NextRecord();
                                });
                        }
                    }
                }
            }
        }

        private List<List<FlattenObjectField>> AggregateTable(List<List<FlattenObjectField>> table)
        {
            // create aggregate table
            var aggTable = new List<List<FlattenObjectField>>();

            // group by specId and iteration results ID
            var groupedTable = from row in table
                               let rowDic = row.ToDictionary(el => el.Key)
                               group row by new { SpecId = rowDic["SpecId"].Value, IterationResultsId = rowDic["IterationResults_Id"].Value } into rowg
                               select rowg;

            // for each group add rows and compute aggregate
            groupedTable.ToList().ForEach(g =>
            {
                // add rows from within the group
                g.ToList().ForEach(row =>
                {
                    aggTable.Add(row);
                });

                // aggregate
                List<FlattenObjectField> aggRow = g.Aggregate(new List<FlattenObjectField>(), (accumulator, row) =>
                {
                    var accumulatorDic = accumulator.ToDictionary(el => el.Key);

                    row.ForEach(col =>
                    {
                        // try get field from accumulator and if not exist then add it
                        FlattenObjectField field = null;
                        if (accumulatorDic.TryGetValue(col.Key, out field) == false)
                        {
                            field = new FlattenObjectField();
                            field.Key = col.Key;
                            accumulator.Add(field);
                        }

                        // accumulate
                        switch (col.Key)
                        {
                            case "Ms":
                            case "IterationResults_Ms":
                                field.Value = (field.Value == null ? 0 : (long)field.Value) + (long)col.Value;
                                break;
                        }
                    });

                    return accumulator;
                });

                // divide by total number of rows
                var aggDic = aggRow.ToDictionary(el => el.Key);

                var msField = aggDic["Ms"];
                if (msField != null)
                {
                    msField.Value = (double)(long)msField.Value / g.Count();
                }

                var iterationResultsMsField = aggDic["IterationResults_Ms"];
                if (iterationResultsMsField != null)
                {
                    iterationResultsMsField.Value = (double)(long)iterationResultsMsField.Value / g.Count();
                }

                // add field to aggregation table
                aggTable.Add(aggRow);
            });

            return aggTable;
        }
    }
}
